﻿using System;
using System.Drawing;
using System.Windows.Forms;

using SharpGL;
using SharpGL.Enumerations;
using SharpGL.Media3D;
using SharpGL.SceneGraph;
using SharpGL.SceneGraph.Assets;
using SharpGL.SceneGraph.Effects;
using SharpGL.SceneGraph.Cameras;
using SharpGL.SceneGraph.Lighting;
using SharpGL.SceneGraph.Primitives;
using SharpGL.SceneGraph.Primitives.Text;
using SharpGL.SceneGraph.Primitives.SmartAxies;
using SharpGL.Serialization;

namespace Visualizer3D
{
    /// <summary>
    /// The main form class.
    /// </summary>
    public partial class SharpGLForm : Form
    {
        #region Private Fields

        private string sceneFilePath = null;

        /// <summary>
        /// The selected scene element.
        /// </summary>
        private SceneElement selectedSceneElement = null;

        #endregion Private Fields

        #region Private Methods

        /// <summary>
        /// Adds the element to tree.
        /// </summary>
        /// <param name="sceneElement">The scene element.</param>
        /// <param name="nodes">The nodes.</param>
        private void AddElementToTree(SceneElement sceneElement, TreeNodeCollection nodes)
        {
            //  Add the element.
            TreeNode newNode = new TreeNode()
            {
                Text = sceneElement.Name,
                Tag = sceneElement
            };
            nodes.Add(newNode);

            //  Add each child.
            foreach (var element in sceneElement.Children)
                AddElementToTree(element, newNode.Nodes);
        }

        private void InitializeNewScene(Scene scene)
        {
            scene.AutoScaleToUnitVolume = true;

            scene.ClearColor = new GLColor(1.0f, 1.0f, 1.0f, 1.0f);

            // Create the camera and set it as the current.
            scene.Camera = new ArcBallCamera()
            {
                Name = "Scene camera",
                Position = new Point3D(0.0f, 0.0f, 1.0f),
                Target = new Point3D(0.0f, 0.0f, 0.0f),
                Near = 0.01f,
                Far = 10.0f,
                UpVector = new Vector3D(0.0f, 1.0f, 0.0f),
                FieldOfView = 60.0f
            };

            // Create a set of scene attributes.
            OpenGLAttributesEffect sceneAttributes = new OpenGLAttributesEffect()
            {
                Name = "Scene attributes"
            };
            // Specify the scene attributes.
            sceneAttributes.AttributesGroup.EnableAttributes.EnableDepthTest = true;
            sceneAttributes.AttributesGroup.EnableAttributes.EnableNormalize = true;
            sceneAttributes.AttributesGroup.EnableAttributes.EnableLighting = true;
            sceneAttributes.AttributesGroup.EnableAttributes.EnableTexture2D = false;
            //sceneAttributes.AttributesGroup.EnableAttributes.EnableBlend = true;
            //sceneAttributes.AttributesGroup.ColorBufferAttributes.BlendingSourceFactor = BlendingSourceFactor.SourceAlpha;
            //sceneAttributes.AttributesGroup.ColorBufferAttributes.BlendingDestinationFactor = BlendingDestinationFactor.OneMinusSourceAlpha;
            sceneAttributes.AttributesGroup.LightingAttributes.TwoSided = true;

            // Add effect for all scene
            scene.SceneContainer.Effects.Add(sceneAttributes);

            BoundingVolume sceneBoundingVolume = scene.SceneContainer.ChildrenBoundingVolume;

            // Create a lights and add them.
            scene.SceneContainer.Children.Add(
                    new Light()
                    {
                        Name = "Light 2",
                        
                        On = true,
                        Position = sceneBoundingVolume.IsEmpty ? new Point3D(0.0f, 0.0f, 0.0f) : sceneBoundingVolume.MinPoint,
                        GLCode = OpenGL.GL_LIGHT0
                    }
                );
            scene.SceneContainer.Children.Move(scene.SceneContainer.Children.Count - 1, 0);

            scene.SceneContainer.Children.Add(
                    new Light()
                    {
                        Name = "Light 1",
                        
                        On = true,
                        Position = sceneBoundingVolume.IsEmpty ? new Point3D(0.0f, 0.0f, 0.0f) : sceneBoundingVolume.MaxPoint,
                        GLCode = OpenGL.GL_LIGHT1
                    }
                );
            scene.SceneContainer.Children.Move(scene.SceneContainer.Children.Count - 1, 0);

            sceneTreeView.Nodes.Clear();
            AddElementToTree(scene.SceneContainer, sceneTreeView.Nodes);
        }

        private Scene LoadScene(string path)
        {
            Scene scene = null;

            try
            {
                scene = SerializationEngine.Instance.LoadScene(path);
            }
            catch
            {
                scene = new Scene();

                System.Windows.Forms.MessageBox.Show("Ошибка чтения сцены из файла.", "Ошибка!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }

            return scene;
        }

        private void SaveScene(Scene scene, string path)
        {
            try
            {
                SerializationEngine.Instance.SaveScene(scene, path);
            }
            catch
            {
                System.Windows.Forms.MessageBox.Show("Ошибка записи сцены в файл.", "Ошибка!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        /// <summary>
        /// Initializes 3D objects of scene.
        /// </summary>
        private void InitializeTestScene(Scene scene)
        {
            // Add root for all new scene objects
            SmartAxies sceneObjectsRoot = new SmartAxies();
            scene.SceneContainer.Children.Add(sceneObjectsRoot);

            // Add some primitives.
            // Add simple axis.
            sceneObjectsRoot.Children.Add(new SharpGL.SceneGraph.Primitives.Axies());

            // Add cube.
            Cube cube = new Cube();
            sceneObjectsRoot.Children.Add(cube);

            // Add piilyline.
            var polyline = new SolidPolyline();
            polyline.GenerateNormals = true;
            polyline.Vertices.Add(new Point3D(10.0f, 2.0f, 6.0f));
            polyline.Vertices.Add(new Point3D(1.0f, 3.0f, 7.0f));
            polyline.Vertices.Add(new Point3D(5.0f, 6.0f, 2.0f));
            polyline.Vertices.Add(new Point3D(7.0f, 2.0f, 10.0f));
            polyline.Vertices.Add(new Point3D(4.0f, 8.0f, 2.0f));
            polyline.Vertices.Add(new Point3D(7.0f, 7.0f, 6.0f));
            var polylineEffect = new OpenGLAttributesEffect()
            {
                Name = "Pilyline attributes"
            };
            polylineEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
            //polylineEffect.LightingAttributes.EnableColorMaterial = true;
            polylineEffect.AttributesGroup.CurrentAttributes.CurrentColor = new GLColor(0.0f, 1.0f, 0.0f, 1.0f);
            polylineEffect.AttributesGroup.LineAttributes.Width = 0.1f;
            polyline.Effects.Add(polylineEffect);
            sceneObjectsRoot.Children.Add(polyline);

            // Add trend.
            var trend = new Trend();
            trend.Position = new Point3D(-3.0f, 2.0f, -3.0f);
            trend.Direction = new Vector3D(0.0f, 0.0f, 1.0f);
            trend.Slices = 8;
            trend.Stacks = 8;
            var trendEffect = new OpenGLAttributesEffect()
            {
                Name = "Trend attributes"
            };
            trendEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
            //trendEffect.LightingAttributes.EnableColorMaterial = true;
            trendEffect.AttributesGroup.CurrentAttributes.CurrentColor = new GLColor(0.0f, 1.0f, 0.0f, 1.0f);
            trendEffect.AttributesGroup.PointAttributes.Size = 2.0f;
            trend.Effects.Add(trendEffect);
            sceneObjectsRoot.Children.Add(trend);

            // Add textured quad.                        
            var texquad = new TexturedQuad(new Point3D(2.0f, 8.0f, 0.0f),
                                           new Point3D(2.0f, 2.0f, 0.0f),
                                           new Point3D(8.0f, 2.0f, 0.0f),
                                           new Point3D(8.0f, 8.0f, 0.0f),
                                           new Size(256, 256));
            texquad.Material = new Material()
            {
                Texture = new Texture2D()
                {
                    Image = new Bitmap(@"test.bmp")
                }
            };

            var texquadEffect = new OpenGLAttributesEffect()
            {
                Name = "texured quad effect"
            };
            texquadEffect.AttributesGroup.EnableAttributes.EnableTexture2D = true;
            texquadEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = false;
            texquadEffect.AttributesGroup.TextureAttributes.TextureTarget = TextureTarget.Texture2D;
            texquadEffect.AttributesGroup.TextureAttributes.TextureMinFilter = TextureMinFilter.Linear;
            texquadEffect.AttributesGroup.TextureAttributes.TextureMagFilter = TextureMagFilter.Linear;
            texquad.Effects.Add(texquadEffect);
            sceneObjectsRoot.Children.Add(texquad);

            // Add 2D text object.
            var text2DObject = new Text2DObject();
            text2DObject.Text = "Какой-то двухмерный текст.";
            text2DObject.Position = polyline.Vertices[3];
            text2DObject.HorizontalAlignment = SharpGL.SceneGraph.Primitives.Text.HorizontalAlignment.Center;
            text2DObject.VerticalAlignment = SharpGL.SceneGraph.Primitives.Text.VerticalAlignment.Top;
            var text2DEffect = new OpenGLAttributesEffect()
            {
                Name = "Text 2D attributes"
            };
            text2DEffect.AttributesGroup.CurrentAttributes.CurrentColor = new GLColor(0.0f, 1.0f, 1.0f, 1.0f);
            //text2DEffect.AttributesGroup.CurrentAttributes.CurrentRasterPosition = polyline.Vertices[3];
            text2DObject.Effects.Add(text2DEffect);
            sceneObjectsRoot.Children.Add(text2DObject);

            // Add 3D text object.
            var text3DObject = new Text3DObject();
            text3DObject.Text = "Тут длинный трехмерный текст.";
            //text3DObject.Font = new Font("Arial", 3.0f, FontStyle.Regular, GraphicsUnit.Point, 204);
            text3DObject.Position = polyline.Vertices[3];
            text3DObject.HorizontalAlignment = SharpGL.SceneGraph.Primitives.Text.HorizontalAlignment.Center;
            text3DObject.VerticalAlignment = SharpGL.SceneGraph.Primitives.Text.VerticalAlignment.Center;
            text3DObject.DepthAlignment = SharpGL.SceneGraph.Primitives.Text.DepthAlignment.Center;
            text3DObject.Extrusion = 1.0f;
            text3DObject.Direction = polyline.Vertices[2] - polyline.Vertices[3];
            text3DObject.Angle = 0.0f;
            var text3DEffect = new OpenGLAttributesEffect()
            {
                Name = "Text 3D attributes"
            };
            text3DEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
            text3DEffect.AttributesGroup.CurrentAttributes.CurrentColor = new GLColor(1.0f, 0.0f, 0.0f, 1.0f);
            text3DObject.Effects.Add(text3DEffect);
            sceneObjectsRoot.Children.Add(text3DObject);

            InitializeNewScene(scene);
        }

        /// <summary>
        /// Called when [selected scene element changed].
        /// </summary>
        private void OnSelectedSceneElementChanged()
        {
            sceneElementPropertyGrid.SelectedObject = SelectedSceneElement;
            sceneControl.Refresh();
        }
        
        private void sceneControl_OpenGLContextInitialized(object sender, EventArgs e)
        {
            sceneControl.CurrentOpenGLContext.FirstFontGlyph = 0;
            sceneControl.CurrentOpenGLContext.NumberOfFontGlyphs = 1279;

            InitializeTestScene(sceneControl.Scene);
        }

        private void sceneControl_Resized(object sender, EventArgs e)
        {
            if (sceneControl.Scene.Camera is ArcBallCamera)
                ((ArcBallCamera)sceneControl.Scene.Camera).ArcBallTransformation.SetBounds(Size);
        }
        
        private void sceneControl_MouseDown(object sender, MouseEventArgs e)
        {
            if (sceneControl.Scene.Camera is ArcBallCamera)
                ((ArcBallCamera)sceneControl.Scene.Camera).ArcBallTransformation.HandleMouseDown(e);
        }

        private void sceneControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (sceneControl.Scene.Camera is ArcBallCamera)
                ((ArcBallCamera)sceneControl.Scene.Camera).ArcBallTransformation.HandleMouseMove(e);
        }

        private void sceneControl_MouseUp(object sender, MouseEventArgs e)
        {
            if (sceneControl.Scene.Camera is ArcBallCamera)
                ((ArcBallCamera)sceneControl.Scene.Camera).ArcBallTransformation.HandleMouseUp(e);
        }

        private void sceneControl_MouseWheel(object sender, MouseEventArgs e)
        {
            if (sceneControl.Scene.Camera is ArcBallCamera)
                ((ArcBallCamera)sceneControl.Scene.Camera).ArcBallTransformation.HandleMouseWheel(e);
        }

        private void newToolStripMenuItem_Click(object sender, EventArgs e)
        {
            sceneControl.Scene = new Scene();
            InitializeNewScene(sceneControl.Scene);

            sceneFilePath = null;
        }
        
        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Show a file open dialog.
            OpenFileDialog openDialog = new OpenFileDialog();
            openDialog.Filter = SerializationEngine.Instance.Filter;
            if (openDialog.ShowDialog() == DialogResult.OK)
            {
                sceneControl.Scene = LoadScene(openDialog.FileName);
                InitializeNewScene(sceneControl.Scene);

                sceneFilePath = openDialog.FileName;
            }
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (sceneFilePath != null)
                SaveScene(sceneControl.Scene, sceneFilePath);
            else
                saveAsToolStripMenuItem_Click(sender, e);
        }

        private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Show a file save dialog.
            SaveFileDialog saveDialog = new SaveFileDialog();
            saveDialog.Filter = SerializationEngine.Instance.Filter;
            if (saveDialog.ShowDialog() == DialogResult.OK)
            {
                SaveScene(sceneControl.Scene, saveDialog.FileName);

                sceneFilePath = saveDialog.FileName;
            }
        }

        private void sceneControl_OpenGLDraw(object sender, PaintEventArgs e)
        {
            if (selectedSceneElement != null && selectedSceneElement is IVolumeBound)
                ((IVolumeBound)selectedSceneElement).BoundingVolume.Render(sceneControl.CurrentOpenGLContext, RenderMode.Render);

            toolStripStatusLabel1.Text = sceneControl.CurrentOpenGLContext.GetError().ToString();
        }
        
        private void sceneTreeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            SelectedSceneElement = e.Node.Tag as SceneElement;
        }

        #endregion Private Methods

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SharpGLForm"/> class.
        /// </summary>
        public SharpGLForm()
        {
            InitializeComponent();
        }

        #endregion Constructors

        #region Public Properties

        /// <summary>
        /// Gets or sets the selected scene element.
        /// </summary>
        /// <value>
        /// The selected scene element.
        /// </value>
        public SceneElement SelectedSceneElement
        {
            get { return selectedSceneElement; }
            private set
            {
                selectedSceneElement = value;
                OnSelectedSceneElementChanged();
            }
        }

        #endregion Public Properties
    }
}
